#include "unixHeader.h"

#include "token.hpp"
#include "hash.hpp"

#include <vector>
#include <iostream>

#include <workflow/WFFacilities.h>
#include <workflow/MySQLResult.h>
#include <workflow/MySQLMessage.h>
#include <wfrest/HttpServer.h>
#include <wfrest/json.hpp>

using namespace std;
using namespace wfrest;


class CloudiskServer
{
public:
    CloudiskServer()
    : _server()
    , _waitGroup(1)
    {}

    void start(unsigned short port) {
        if(_server.track().start(port) == 0) {
            _server.list_routes();
            _waitGroup.wait();
            _server.stop();
        } else {
            printf("CloudiskServer start failed.\n");
        }
    }

    void loadMudules() {
        loadStaticResources();
        loadSignupModule();
        loadSigninModule();
        loadUserInfoModule();
        loadUserFileListModule();
        loadFileUploadModule();
        loadFileDownloadModule();
    }

private: 
    void loadStaticResources();
    void loadSignupModule();
    void loadSigninModule();
    void loadUserInfoModule();
    void loadUserFileListModule();
    void loadFileUploadModule();
    void loadFileDownloadModule();
private:
    HttpServer _server;
    WFFacilities::WaitGroup _waitGroup;
};

void CloudiskServer::loadStaticResources() 
{
    _server.GET("/user/signup", [](const HttpReq * , HttpResp * resp) {
        resp->File("./static/view/signup.html");
    });
    _server.GET("/static/view/signin.html", [](const HttpReq * , HttpResp * resp){
        resp->File("./static/view/signin.html");
    });

    _server.GET("/static/view/home.html", [](const HttpReq * , HttpResp * resp){
        resp->File("./static/view/home.html");
    });

    _server.GET("/static/js/auth.js", [](const HttpReq * , HttpResp * resp){
        resp->File("./static/js/auth.js");
    });

    _server.GET("/static/img/avatar.jpeg", [](const HttpReq * , HttpResp * resp){
        resp->File("./static/img/avatar.jpeg");
    });

    _server.GET("/file/upload", [](const HttpReq * , HttpResp * resp){
        resp->File("./static/view/index.html");
    });

    _server.GET("/file/upload/success", [](const HttpReq * , HttpResp * resp){
        resp->headers["Content-Type"] = "text/html";
        resp->String("<html><body>Upload Success!!!</body></html>");
    });
}

void CloudiskServer::loadSignupModule()
{
    _server.POST("/user/signup", [](const HttpReq * req, HttpResp * resp, SeriesWork * series){
        if(req->content_type() == APPLICATION_URLENCODED) {
            //1. 解析请求
            auto & formKV = req->form_kv();
            std::string username = formKV["username"];
            std::string password = formKV["password"];
            //2. 对密码进行加密
            string salt("12345678");
            string encodedPasswd(crypt(password.c_str(), salt.c_str()));
            printf("encoded: %s\n", encodedPasswd.c_str());
            //3. 将用户信息写入数据库中
            string url("mysql://root:1234@localhost");
            auto mysqlTask = WFTaskFactory::create_mysql_task(url, 1, nullptr);
            string query = "INSERT INTO cloudisk.tbl_user(user_name, user_pwd, status) VALUES('";
            query += username + "', '" + encodedPasswd + "', 0);";
            printf("sql: %s\n", query.c_str());
            mysqlTask->get_req()->set_query(query);
            series->push_back(mysqlTask);
            //4. 生成响应
            resp->String("SUCCESS");
        }else {
            resp->String("Signup Failed");
        }
    });
}

void CloudiskServer::loadSigninModule()
{
    _server.POST("/user/signin", [](const HttpReq * req, HttpResp * resp, SeriesWork * series){
        //1. 解析请求，获取用户名和密码
        if(req->content_type() == APPLICATION_URLENCODED) {
            auto & formKV = req->form_kv();
            string username = formKV["username"];
            string password = formKV["password"];
            //2. 对密码进行加密
            string salt("12345678");
            string encodedPasswd = crypt(password.c_str(), salt.c_str());
            //3. 访问MySQL,进行登录验证
            string url("mysql://root:1234@localhost");
            auto mysqlTask = WFTaskFactory::create_mysql_task(url, 1, 
            [resp, encodedPasswd, username, salt, url, series](WFMySQLTask * task){
                //0. 对任务进行判断...
                using namespace protocol;
                using std::vector;
                MySQLResultCursor cursor(task->get_resp());
                //1. 读取结果集
                vector<vector<MySQLCell>> rows;
                cursor.fetch_all(rows);
                if(rows[0][0].is_string()) {
                    string result = rows[0][0].as_string();
                    if(result == encodedPasswd) {
                        //2. 登录成功
                        //2.0生成Token
                        Token token(username, salt);
                        string strToken = token.genToken();
                        //2.1将Token写入数据库
                        auto mysqlTask2 = WFTaskFactory::create_mysql_task(url, 1, nullptr);
                        string sql2 = "REPLACE INTO cloudisk.tbl_user_token(user_name, user_token) VALUES('"
                        + username + "', '" + strToken + "');";
                        cout << "sql2:\n" << sql2 << endl;
                        mysqlTask2->get_req()->set_query(sql2);
                        series->push_back(mysqlTask2);
                        //2.2生成响应信息,发送给客户端
                        Json respMsg;
                        Json data;
                        data["Token"] = strToken;
                        data["Username"] = username;
                        data["Location"] = "/static/view/home.html";
                        respMsg["data"] = data;
                        resp->String(respMsg.dump());
                    } else {
                        //验证失败
                        printf("password error.\n");
                        resp->String("error");
                    }
                } else {
                    //验证失败
                    printf("server 500 error.\n");
                    resp->set_status_code("500");
                    resp->set_reason_phrase("Internal Server Error");
                }
            });
            string sql = "select user_pwd from cloudisk.tbl_user where user_name='";
            sql += username + "' limit 1;";
            cout << "sql:\n" << sql << endl;
            mysqlTask->get_req()->set_query(sql);
            series->push_back(mysqlTask);
        }
    });
}

void CloudiskServer::loadUserInfoModule()
{
    _server.GET("/user/info", [](const HttpReq * req, HttpResp * resp){
        //1. 解析请求
        string username = req->query("username");
        string token = req->query("token");
        printf("username: %s\n", username.c_str());
        printf("token: %s\n", token.c_str());
        //2. 校验token
        
        //3. 查询数据库，获取用户的注册时间
        string url("mysql://root:1234@localhost");
        string sql("select signup_at from cloudisk.tbl_user where user_name = '");
        sql += username + "' limit 1;";
        cout << "sql:\n" << sql << endl;

        using namespace protocol;
        resp->MySQL(url, sql, [resp, username](MySQLResultCursor * pcursor){
            using std::vector;
            vector<vector<MySQLCell>> rows;
            pcursor->fetch_all(rows);

            if(rows[0][0].is_datetime()) {
                Json respMsg;
                Json data;
                data["Username"] = username;
                data["SignupAt"] = rows[0][0].as_datetime();
                respMsg["data"] = data;
                resp->String(respMsg.dump());
            } else {
                resp->String("User info get failed");
            }
        });
    });
}

void CloudiskServer::loadUserFileListModule()
{
    _server.POST("/file/query", [](const HttpReq * req, HttpResp * resp){
        //1. 解析请求
        string username = req->query("username");
        string token = req->query("token");
        auto & formKV = req->form_kv();
        string limitcnt = formKV["limit"];
        cout << "username:" << username << endl;
        cout << "token:" << token << endl;
        cout << "limitcnt:" << limitcnt << endl;
        //2. 校验token
        //...
        //3. 查询数据库
        string sql("select file_sha1, file_name, file_size, upload_at, last_update ");
        sql += " from cloudisk.tbl_user_file where user_name = '" + username;
        sql += "' limit " + limitcnt + ";";
        cout << "sql:\n" << sql << endl;
        string url("mysql://root:1234@localhost");
        using namespace protocol;
        resp->MySQL(url, sql, [resp, username](MySQLResultCursor * pcursor){
            using std::vector;
            vector<vector<MySQLCell>> rows;
            pcursor->fetch_all(rows);
            if(rows.size() == 0) return;

            Json arrMsg;
            for(size_t i = 0; i < rows.size(); ++i)  {
                Json row;
                row["FileHash"] = rows[i][0].as_string();
                row["FileName"] = rows[i][1].as_string();
                row["FileSize"] = rows[i][2].as_ulonglong();
                row["UploadAt"] = rows[i][3].as_datetime();
                row["LastUpdated"] = rows[i][4].as_datetime();
                arrMsg.push_back(row);//将arrMsg当成数组来使用
            }  
            resp->String(arrMsg.dump());
        });
    });
}

void CloudiskServer::loadFileUploadModule()
{
    //
    _server.POST("/file/upload", [](const HttpReq * req, HttpResp * resp, SeriesWork * series){
        //1. 解析请求
        if(req->content_type() == MULTIPART_FORM_DATA) {
            auto & form = req->form();
            auto pairKV = form["file"];
            cout << "filename: " <<  pairKV.first << endl;
            /* cout << "content: " << pairKV.second << endl; */
            string filename = form["file"].first;
            string content = form["file"].second;
            //2. 将文件内容写入本地
            mkdir("./tmp", 0755);
            string filepath = "./tmp/" + filename;
            int fd = open(filepath.c_str(), O_CREAT|O_RDWR, 0644);
            if(fd < 0) {
                perror("open");
                resp->String("upload file error");
                return;
            }
            int ret = write(fd, content.c_str(), content.size());
            close(fd);

            //3. 重定向到上传成功的页面
            resp->headers["Location"] = "/file/upload/success";
            resp->headers["Content-Type"] = "text/html";
            resp->set_status_code("301");
            resp->set_reason_phrase("Moved Permanently");
            //4. 将文件信息更新到数据库中
            //4.1 用户名
            string username = req->query("username");
            //4.2 文件的hash值
            Hash hash(filepath);
            string filehash = hash.sha1();
            string url("mysql://root:1234@localhost");
            auto mysqlTask = WFTaskFactory::create_mysql_task(url, 1, nullptr);
            string sql = "INSERT INTO cloudisk.tbl_user_file(user_name, file_sha1,file_size, file_name, status)VALUES('";
            sql += username + "', '" + filehash + "', " + std::to_string(content.size()) + ",'" + filename + "', 0);";
            cout << "sql:\n";
            cout << sql << endl;
            mysqlTask->get_req()->set_query(sql);
            series->push_back(mysqlTask);
        }
    });
}

void CloudiskServer::loadFileDownloadModule()
{
    _server.GET("/file/downloadurl", [](const HttpReq * req, HttpResp * resp){
        //1.解析请求
        string filename = req->query("filename");
        cout << "filename: " << filename << endl;
        string filepath = "./tmp/" + filename;
        //tmp/filename
#if 0
        //2. 读取文件
        int fd = open(filepath.c_str(), O_RDONLY);
        if(fd < 0) {
            perror("open");
            return;
        }
        char buff[1024] = {0};
        int ret = read(fd, buff, sizeof(buff));
        close(fd);

        //3. 设置下载形式
        resp->headers["Content-Type"] = "application/octet-stream";//内容为二进制
        // 以附件形式进行下载
        resp->headers["Content-Disposition"] = "attachment;filename=" + filename;
        resp->append_output_body(buff, ret);
#endif
        //第二种方案：生成下载链接
        string downloadurl = "http://192.168.30.128:8888/" + filename;
        resp->String(downloadurl);
    });
}

void test0()
{
    CloudiskServer server;
    server.loadMudules();
    server.start(8080);
}

int main()
{
    test0();
    return 0;
}

